package com.lin.demo.service.task;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author zimingl
 * @Date 2023/4/2 16:08
 * @Description: task test
 * <p>
 * name 用来标注一个定时服务的名字，被用于写入数据库作为区分不同服务的标识，如果有多个同名定时任务则同一时间点只有一个执行成功
 * lockAtMostFor 成功执行任务的节点所能拥有独占锁的最长时间，单位是毫秒ms
 * lockAtMostForString 成功执行任务的节点所能拥有的独占锁的最长时间的字符串表达，例如“PT5M”表示为5分钟
 * lockAtLeastFor 成功执行任务的节点所能拥有独占锁的最短时间，单位是毫秒ms
 * lockAtLeastForString 成功执行任务的节点所能拥有的独占锁的最短时间的字符串表达，例如“PT5M”表示为5分钟
 * lockAtLeastForString的作用是为了防止在任务开始之初由于各个服务器同名任务的服务器时间差，启动时间差等这些造成的一些问题，有了这个时间设置后，
 * 就可以避免因为上面这些小的时间差造成的一些意外，保证一个线程在抢到锁后，即便很快执行完，也不要立即释放，留下一个缓冲时间。
 * 这样等多个线程都启动后，由于任务已经被锁定，其他没有获得锁的任务也不会再去抢锁。注意这里的时间不要设置几秒几分钟，尽量大些
 * lockAtMostForString 这个设置的作用是为了防止抢到锁的那个线程，因为一些意外死掉了，而锁又始终不被释放。
 * 这样的话，虽然当前执行周期虽然失败了，但以后的执行周期如果这里一直不释放的话，后面就永远执行不到了。
 * 它的目的不在于隐藏任务，更重要的是，释放锁，并且查找解决问题。
 * 至于是否带有string后缀，只是2种表达方式，数字类型的就是毫秒数，字符串类型的就有自己固定的格式 ，例如：PT30S  30s
 * @SchedulerLock(name = "mp-demo-scheduled-test", lockAtMostFor = "PT1M"), cron = "0 0/30 * * * ? ",
 * 那么 AM 8:00 redis set key(mp-demo-scheduled-test) value 1分钟
 * <p>
 * 服务有两个部署实例，假如实例A 时间 AM 8:00， 实例B 时间 AM 8:10 cron = "0 0/30 * * * ? " 那么 lockAtMostFor value
 * 要大于20分钟，否则，两个实例会跑两次
 * <p>
 * 下面有种特殊情况，如果task任务的执行时间超过 lockAtMostFor 如果调度比较频繁，那么就会实例A拿到锁开始执行任务还没结束，
 * 实例B也会开始了下一轮执行
 */
//@Component
@Slf4j
public class ScheduledTest {
    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    @Scheduled(cron = "0/10 * * * * ? ")
    @SchedulerLock(name = "mp-demo-scheduled-test", lockAtMostFor = "PT40S")
    @Async
    @SneakyThrows
    public void test() {
        log.info(">>>>>> 定时任务1");
        Thread.sleep(60000);
        String nowDate = LocalDateTime.now().format(formatter);
        log.info(">>>>>> 北京时间: {}", nowDate);
    }

    @Scheduled(cron = "0/10 * * * * ? ")
    @SchedulerLock(name = "mp-demo-scheduled-test", lockAtMostFor = "PT40S")
    @Async
    @SneakyThrows
    public void test2() {
        log.info(">>>>>> 定时任务2");
        Thread.sleep(60000);
        String nowDate = LocalDateTime.now().format(formatter);
        log.info(">>>>>> 北京时间: {}", nowDate);
    }
}
